package com.ship.dispatch.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ship.common.business.service.impl.BaseServiceImpl;
import com.ship.common.core.constant.ShipApiConstants;
import com.ship.common.core.domain.R;
import com.ship.common.core.domain.SpPortData;
import com.ship.common.core.enums.MessageTypeEnum;
import com.ship.common.core.enums.ModuleUrlEnum;
import com.ship.common.core.exception.BaseException;
import com.ship.common.core.utils.BeanCopyUtils;
import com.ship.common.core.utils.DateUtils;
import com.ship.common.core.utils.OkHttpUtil;
import com.ship.common.core.web.domain.JsonResult;
import com.ship.common.security.utils.SecurityUtils;
import com.ship.dispatch.bean.*;
import com.ship.dispatch.bo.*;
import com.ship.dispatch.mapper.*;
import com.ship.dispatch.model.SpDispatchSchemeDetailModel;
import com.ship.dispatch.model.SpDispatchSchemeSaveModel;
import com.ship.dispatch.model.Vo.ShipLoadDaysAndRouteVo;
import com.ship.dispatch.model.Vo.ShipRouteSearchVo;
import com.ship.dispatch.model.dto.DispatchPotentialSchemeChartSaveDto;
import com.ship.dispatch.service.*;
import com.ship.dispatch.util.CommonUtil;
import com.ship.dispatch.util.IDUtil;
import com.ship.system.api.domain.SysMessageSend;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import org.apache.commons.collections4.map.HashedMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import org.apache.poi.ss.usermodel.*;

/**
 * <p>
 * 方案详情 服务实现类
 * </p>
 *
 * @author AbyssRabbit@163.com
 * @since 2023-10-17
 */
@Service
@Slf4j
public class SpDispatchPotentialSchemeDetailServiceImpl extends BaseServiceImpl<SpDispatchPotentialSchemeDetailMapper
        , SpDispatchPotentialSchemeDetail> implements SpDispatchPotentialSchemeDetailService {

    @Autowired
    @Lazy
    private SpInformationService spInformationService;

    @Autowired
    private SpShipPotentialScheduleService shipScheduleService;

    @Autowired
    private SpDispatchPotentialService spDispatchService;

    @Autowired
    private SpDispatchService dispatchService;

    @Autowired
    private SpShipPotentialScheduleService potentialScheduleService;

    @Autowired
    private SpShipScheduleService scheduleService;

    @Autowired
    private SpDispatchSchemeDetailService dispatchSchemeDetailService;

    @Autowired(required = false)
    private ConShippingMapper conShippingMapper;

    @Autowired(required = false)
    private ConResourceMemorandumMapper resourceMemorandumMapper;

    @Autowired(required = false)
    private SpPortInformationMapper portInformationMapper;

    @Autowired
    private SpPortRouteDistanceService spPortRouteDistanceService;


    private static String shipServerHost;


    private static String shipServerAppKey;

    @Value("${ship_server.host}")
    public void setShipServerHost(String shipServerHost) {
        this.shipServerHost = shipServerHost;
    }

    @Value("${ship_server.app_key}")
    public void setShipServerAppKey(String shipServerAppKey) {
        this.shipServerAppKey = shipServerAppKey;
    }


    @Override
    public JsonResult schemeDetailUpdate(SpDispatchSchemeDetailBO dto) {
        if(dto.getId() == null){
            return JsonResult.failed("详情ID不能为空！");
        }
        SpDispatchPotentialSchemeDetail schemeDetail = this.getBaseMapper().selectById(dto.getId());
        if(schemeDetail == null){
            throw new BaseException("方案信息异常！");
        }
        BeanCopyUtils.copy(dto,schemeDetail);
        if(schemeDetail.getSellerResourceContract() != null){
            //绑定销售合同
            ConResourceMemorandum resourceMemorandum = resourceMemorandumMapper.getById(schemeDetail.getSellerResourceContract());
            if(resourceMemorandum == null){
                return JsonResult.failed("销售合同信息异常！");
            }
            schemeDetail.setSellPrice(resourceMemorandum.getContractPrice());
            schemeDetail.setSellerResourceContract(resourceMemorandum.getId());
            schemeDetail.setSellerContractName(resourceMemorandum.getContractName());
            schemeDetail.setSellerTradeType(resourceMemorandum.getTradeType());
            schemeDetail.setSellerTradeBody(resourceMemorandum.getTradeBody());
            if(resourceMemorandum.getContractPrice() != null){
                //贸易利润率=（销售价格*实际卸载量）-（实际装载量*合同价格）
                BigDecimal tradingProfit = BigDecimal.ZERO;
                if(schemeDetail.getSingleGoodsQuantity() != null && schemeDetail.getConPrice() != null && schemeDetail.getRealityUnloadingCapacity() != null){
                    tradingProfit = schemeDetail.getSellPrice().multiply(schemeDetail.getRealityUnloadingCapacity())
                            .subtract(schemeDetail.getSingleGoodsQuantity().multiply(schemeDetail.getConPrice()));
                }
                tradingProfit = tradingProfit.divide(new BigDecimal("10000"),2,RoundingMode.HALF_UP);
                schemeDetail.setTradingProfit(tradingProfit);
            }
            if(resourceMemorandum.getDeliveryType() != null && resourceMemorandum.getDeliveryType().intValue() == 1){
                if(resourceMemorandum.getTradeModel() != null && (resourceMemorandum.getTradeModel().intValue() == 2 || resourceMemorandum.getTradeModel().intValue() == 3)){
                    schemeDetail.setIsFobSell(1);
                }
            }
            if(resourceMemorandum.getContractName().indexOf("返装销售") > -1 && schemeDetail.getVoyageNo().indexOf("R") < 0){
                schemeDetail.setVoyageNo(schemeDetail.getVoyageNo()+"R");
            }
            if(resourceMemorandum.getContractName().indexOf("返装销售") > -1 && schemeDetail.getGoodsNum().indexOf("R") < 0){
                schemeDetail.setGoodsNum(schemeDetail.getGoodsNum()+"R");
            }
        }

        if(schemeDetail.getExpectedLoadingPorts() != null && schemeDetail.getExpectedLoadingPorts().size() > 0
                && schemeDetail.getExpectedUnloadingPorts() != null && schemeDetail.getExpectedUnloadingPorts().size() > 0) {
            List<Long> startPortList = schemeDetail.getExpectedLoadingPorts().stream()
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            List<Long> endPortList = schemeDetail.getExpectedUnloadingPorts().stream()
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            ShipLoadDaysAndRouteVo shipLoadDaysAndRouteVo = spPortRouteDistanceService.getCommonLoadDay(startPortList
                    , endPortList, schemeDetail.getShipSpeed(), schemeDetail.getPassStrait(), schemeDetail.getPassArea());

            if (shipLoadDaysAndRouteVo == null) {
                throw new BaseException("暂无航线信息！");
            }
            //装载港
            schemeDetail.setExpectedLoadingPort(shipLoadDaysAndRouteVo.getStartPortName());
            schemeDetail.setExpectedLoadingPortId(shipLoadDaysAndRouteVo.getStartPortId());
            //卸载港
            schemeDetail.setExpectedUnloadingPort(shipLoadDaysAndRouteVo.getEndPortName());
            schemeDetail.setExpectedUnloadingPortId(shipLoadDaysAndRouteVo.getEndPortId());
            schemeDetail.setPassStrait(shipLoadDaysAndRouteVo.getPassStrait());
            schemeDetail.setPassArea(shipLoadDaysAndRouteVo.getPassArea());
            schemeDetail.setExpectedLoadThirdId(shipLoadDaysAndRouteVo.getStartThirdPortId());
            schemeDetail.setExpectedUnloadThirdId(shipLoadDaysAndRouteVo.getEndThirdPortId());
            schemeDetail.setUnloadDistance(shipLoadDaysAndRouteVo.getTrackDistance());

            int shipDays = 0;
            int chinaDays = 0;
            int europeDays = 0;
            int indiaDays = 0;

            if (shipLoadDaysAndRouteVo.getShipDay() != null) {
                shipDays = shipLoadDaysAndRouteVo.getShipDay();
            }
            if (shipLoadDaysAndRouteVo.getChinaDays() != null) {
                chinaDays = shipLoadDaysAndRouteVo.getChinaDays();
            }
            if (shipLoadDaysAndRouteVo.getEuropeDays() != null) {
                europeDays = shipLoadDaysAndRouteVo.getEuropeDays();
            }
            if (shipLoadDaysAndRouteVo.getIndiaDays() != null) {
                indiaDays = shipLoadDaysAndRouteVo.getIndiaDays();
            }

            Date expectLoadDate = schemeDetail.getExpectedLoadingDate();
            schemeDetail.setExpectedLoadingDate(expectLoadDate);
            schemeDetail.setExpectedLoadingCompletionWindow(DateUtil.offsetDay(expectLoadDate, 2));

            //预计卸载时间
            schemeDetail.setExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, shipDays));
            //中国预计卸载时间
            schemeDetail.setChinaExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, chinaDays));
            //印度预计卸载时间
            schemeDetail.setIndiaUnloadTime(DateUtil.offsetDay(expectLoadDate, indiaDays));
            //欧洲预计卸载时间
            schemeDetail.setEuropeUnloadTime(DateUtil.offsetDay(expectLoadDate, europeDays));
            //预计满载航程天数
            schemeDetail.setExpectedFullLoadVoyageDays(shipDays);
            //印度空载航程天数
            schemeDetail.setIndiaEmptyLoadDays(indiaDays);
            //欧洲空载航程天数
            schemeDetail.setEuropeEmptyLoadDays(europeDays);
            //预计卸载完成窗口
            schemeDetail.setExpectedUnloadingCompletionWindow(DateUtil.offsetDay(schemeDetail.getExpectedUnloadingWindow(), 2));

            //预计空载返程时间
            schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~shipDays + 1));
            //中国预计空载返程窗口
            schemeDetail.setChinaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~chinaDays + 1));
            //欧洲预计空载返程窗口
            schemeDetail.setEuropeExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~europeDays + 1));
            //印度预计空载返程窗口
            schemeDetail.setIndiaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~indiaDays + 1));

            //预计空载航程天数
            SpDispatchPotentialSchemeDetail lastSchemeDetail = this.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpDispatchPotentialSchemeDetail>()
                            .lt(SpDispatchPotentialSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                            .eq(SpDispatchPotentialSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                            .eq(SpDispatchPotentialSchemeDetail::getImo,schemeDetail.getImo())
                            .orderByDesc(SpDispatchPotentialSchemeDetail::getExpectedLoadingDate)
                            .last("limit 1")
            );
            schemeDetail.setLastShipSpeed(schemeDetail.getShipSpeed());
            if(lastSchemeDetail == null){
                //没有上一船货，则没有空载时间
                schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());

                //没有空载时间则清空空载航线信息
                schemeDetail.setEmptyDistance(0);
                schemeDetail.setLastUnloadPortName("");
            }else{
                ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),lastSchemeDetail.getExpectedUnloadThirdId()
                        ,schemeDetail.getExpectedLoadThirdId(),null,null);
                if(routeDay != null){
                    int day = routeDay.getShipRouteDay().intValue();
                    schemeDetail.setEmptyDistance(routeDay.getTrackDistance());
                    schemeDetail.setLastUnloadPortName(lastSchemeDetail.getExpectedUnloadingPort());
                    schemeDetail.setExpectedEmptyLoadVoyageDays(day);
                    schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~day + 1));
                }else{
                    schemeDetail.setEmptyDistance(0);
                    schemeDetail.setLastUnloadPortName("");
                    schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                    schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());
                }
            }

        }

        this.getBaseMapper().updateById(schemeDetail);

        //重新生成航次号和货号
        List<SpDispatchPotentialSchemeDetail> detailList = this.baseMapper.selectList(
                new LambdaQueryWrapper<SpDispatchPotentialSchemeDetail>()
                        .eq(SpDispatchPotentialSchemeDetail::getSchemeId, schemeDetail.getSchemeId())
                        .ge(SpDispatchPotentialSchemeDetail::getExpectedLoadingDate, DateUtils.parseDateToStr("yyyy-MM-dd",schemeDetail.getExpectedLoadingDate()))
        );
        if (detailList.size() > 0) {
            detailList = CommonUtil.resetVoyageNoAndGoodsNumberPotential(detailList);

            detailList = resetSetLastEmptyInfo(detailList,schemeDetail.getExpectedLoadingDate());

            //重新计算一下前后空挡信息
            detailList = getIntervalDays(detailList,null);

            //计算优化成本及船舶利用率
            detailList = getUseRateAndDispatchCost(detailList,null);

            this.updateBatchById(detailList);
        }

        return JsonResult.success();

    }

    @Override
    public JsonResult schemeDetailDelete(Long detailId,String reasonMsg) {
        SpDispatchPotentialSchemeDetail spDispatchSchemeDetail = this.getBaseMapper().selectById(detailId);
        if(spDispatchSchemeDetail == null){
            throw new BaseException("方案信息异常！");
        }
        this.baseMapper.deleteById(detailId);
        return JsonResult.success();
    }


    @Override
    public JsonResult schemeSave(SpDispatchSchemeSaveModel dto) {
        if(dto.getSchemeDetailList() == null || dto.getSchemeDetailList().size() == 0){
            return JsonResult.failed("方案信息不能为空！");
        }
        //获取当前可用船只信息
        Map<String,List<SpInformation>> shipsMap = spInformationService.getConSpInformation(new Date());
        //可用正常船只
        List<SpInformation> normalShips = shipsMap.get("normalShips");
        //可用的虚拟船只
        List<SpInformation> virtualShips = shipsMap.get("virtualShips");

        if(normalShips.size()+virtualShips.size() == 0){
            //创建虚拟船
            SpInformation virtualShip = spInformationService.createVirtualSpInformation();
            virtualShips.add(virtualShip);
        }

        //获取个人当前调度方案信息
        SpDispatchPotentialScheme spDispatchScheme = spDispatchService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpDispatchPotentialScheme>()
                        .eq(SpDispatchPotentialScheme::getIsHistory,0)
                        .and(qw -> {
                            qw.eq(SpDispatchPotentialScheme::getCreateBy, SecurityUtils.getUsername());
                            qw.or().eq(SpDispatchPotentialScheme::getIsShare,1);
                        })
                        .orderByDesc(SpDispatchPotentialScheme::getCreateDate)
                        .last("limit 1")
        );
        if(spDispatchScheme == null){
            //没有发布过年度计划则新增
            spDispatchScheme = new SpDispatchPotentialScheme();
            spDispatchScheme.setId(IDUtil.nextId());
            spDispatchScheme.setCreateDate(new Date());
            spDispatchScheme.setCreateBy(SecurityUtils.getUsername());
            spDispatchScheme.setNumber(1);
            spDispatchScheme.setIsShare(1);
            spDispatchScheme.setIsHistory(0);
            spDispatchScheme.setIsDistribute(1);
            spDispatchService.save(spDispatchScheme);
        }
        String tipString = "";
        for (SpDispatchSchemeDetailModel schemeModel : dto.getSchemeDetailList()) {
            // 查询当年年度计划
            Integer year = DateUtil.year(schemeModel.getExpectedLoadingDate());
            //单位为立方米时转为百万英热
            if (schemeModel.getQuantityUnit() != null && schemeModel.getQuantityUnit().equals("2")) {
                BigDecimal goodsQuantityNew = schemeModel.getGoodsQuantity().multiply(BigDecimal.valueOf(0.985))
                        .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                        .setScale(0, BigDecimal.ROUND_DOWN);
                schemeModel.setGoodsQuantity(goodsQuantityNew);
            }

            List<SpInformation> spInformation = new LinkedList<>();

            ConResourceMemorandum resourceMemorandum = null;
            // 根据条件筛选-判断资源合同中装卸量是否满足船只
            if(schemeModel.getConId() != null){
                resourceMemorandum = resourceMemorandumMapper.selectById(schemeModel.getConId());
                if(resourceMemorandum == null){
                    return JsonResult.failed("合同信息异常！");
                }
            }

            //是否是直接使用的虚拟船
            Boolean useVirtualShip = false;
            if(normalShips.size() > 0){
                for (SpInformation ship : normalShips) {
                    // 根据条件筛选-判断船舱容是否为空
                    if (ship.getStorageCapacity() == null) {
                        continue;
                    }
                    // 根据条件筛选-判断IMO
                    if (schemeModel.getImo() != null && schemeModel.getImo().size() > 0) {
                        if(!schemeModel.getImo().contains(ship.getImo())){
                            //IMO不一致
                            continue;
                        }
                    }
                    // 根据条件筛选-判断船旗国
                    if (schemeModel.getFlagState() != null && schemeModel.getFlagState().size() > 0) {
                        if(!schemeModel.getFlagState().contains(ship.getCountry())){
                            //船旗国不一致
                            continue;
                        }
                    }
                    // 根据条件筛选-判断油耗
                    if (schemeModel.getOilConsumption() != null) {
                        if (ship.getOilConsumption().compareTo(schemeModel.getOilConsumption()) < 0) {
                            continue;
                        }
                    }

                    BigDecimal mut = ship.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                            .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                            .setScale(0, BigDecimal.ROUND_DOWN);
//                    if (resourceMemorandum != null && resourceMemorandum.getLoadingOrUnloadingCapacity().compareTo(mut) < 0) {
//                        //合同限定的装卸载量必须大于船只的舱容（这样才能保证我排的船肯定可以把货拉回来）
//                        continue;
//                    }

                    // 根据条件筛选-判断船型起止
                    if (schemeModel.getShipModelStart() != null) {
                        if (schemeModel.getShipModelStart().compareTo(ship.getStorageCapacity()) > 0) {
                            continue;
                        }
                    }
                    if (schemeModel.getShipModelEnd() != null) {
                        if (ship.getStorageCapacity().compareTo(schemeModel.getShipModelStart()) < 0) {
                            continue;
                        }
                    }
                    spInformation.add(ship);
                }
                if(spInformation.size() == 0){
                    //所有船只都不符合条件则选择虚拟船来进行分配
                    useVirtualShip = true;
                    spInformation.addAll(virtualShips);
                }
            }else{
                useVirtualShip = true;
                spInformation.addAll(virtualShips);
            }
            // 处理大船和小船
            List<SpInformation> maxShips = new ArrayList<>();
            List<SpInformation> minShips = new ArrayList<>();
            for (SpInformation sp : spInformation) {
                if (sp.getStorageCapacity().compareTo(new BigDecimal(160000)) > 0) {
                    maxShips.add(sp);
                } else {
                    minShips.add(sp);
                }
            }
            //大小船根据舱容来排序（倒序）
            maxShips = maxShips.stream().sorted(Comparator.comparing(SpInformation::getStorageCapacity).reversed()).collect(Collectors.toList());
            minShips = minShips.stream().sorted(Comparator.comparing(SpInformation::getStorageCapacity).reversed()).collect(Collectors.toList());

            BigDecimal allGoodsQuantity = schemeModel.getGoodsQuantity();

            //剩余货量
            BigDecimal goodsQuantity = schemeModel.getGoodsQuantity();

            List<SpDispatchPotentialSchemeDetail> insertData = new LinkedList<>();
            Date loadDate = schemeModel.getExpectedLoadingDate();

            ShipLoadDaysAndRouteVo loadDays = spPortRouteDistanceService.getCommonLoadDay(schemeModel.getLoadingSort()
                    ,schemeModel.getUnloadSort(),schemeModel.getSpeed(),schemeModel.getPassStrait(),schemeModel.getPassArea());
            if(loadDays == null){
                return JsonResult.failed("当前航线暂无可匹配的路线信息！");
            }

            SpDispatchPotentialSchemeDetail schemeDetail = initDispatchSchemeDetail(year,resourceMemorandum
                    ,spDispatchScheme.getId(),loadDate,schemeModel,loadDays);
            schemeDetail.setPlanId(spDispatchScheme.getPlanId());

            int month = DateUtil.month(loadDate) + 1;

            SpInformation loadShipInfo = null;
            //根据装载月份来判断大小船的优先级
            if (month >= 4 && month <= 10) {
                //优先大船
                if(maxShips.size() > 0){
                    for (SpInformation ship : maxShips) {
                        if (schemeModel.getImo() != null && schemeModel.getImo().size() > 0
                                && !schemeModel.getImo().contains(ship.getImo())) {
                            continue;
                        }
                        if(checkShipTimeWindows(ship,schemeDetail)){
                            loadShipInfo = ship;
                            break;
                        }
                    }
                }
                if(loadShipInfo == null && minShips.size() > 0){
                    //大船不符合，则派小船
                    for (SpInformation ship : minShips) {
                        if (schemeModel.getImo() != null && schemeModel.getImo().size() != 0
                                && !schemeModel.getImo().contains(ship.getImo())) {
                            continue;
                        }
                        if(checkShipTimeWindows(ship,schemeDetail)){
                            loadShipInfo = ship;
                            break;
                        }
                    }
                }
            }else{
                //优先小船
                if(minShips.size() > 0){
                    for (SpInformation ship : minShips) {
                        if (schemeModel.getImo() != null && schemeModel.getImo().size() != 0
                                && !schemeModel.getImo().contains(ship.getImo())) {
                            continue;
                        }
                        if(checkShipTimeWindows(ship,schemeDetail)){
                            loadShipInfo = ship;
                            break;
                        }
                    }
                }
                if(loadShipInfo == null && maxShips.size() > 0){
                    //小船都不符合，则派大船
                    for (SpInformation ship : maxShips) {
                        if (schemeModel.getImo() != null && schemeModel.getImo().size() != 0
                                && !schemeModel.getImo().contains(ship.getImo())) {
                            continue;
                        }
                        if(checkShipTimeWindows(ship,schemeDetail)){
                            loadShipInfo = ship;
                            break;
                        }
                    }
                }
            }

            if(loadShipInfo == null){
                //没有可用的船，走虚拟船逻辑
                if(!useVirtualShip && virtualShips.size() > 0){
                    insertData = virtualShipDispatch(virtualShips,schemeModel
                            ,goodsQuantity,spDispatchScheme.getPlanId(), year
                            , resourceMemorandum, spDispatchScheme.getId(),loadDays);
                }else{
                    List<SpInformation> ships = new LinkedList<>();
                    SpInformation virtualShip = spInformationService.createVirtualSpInformation();
                    ships.add(virtualShip);
                    insertData = virtualShipDispatch(ships,schemeModel
                            ,goodsQuantity,spDispatchScheme.getPlanId(), year
                            , resourceMemorandum, spDispatchScheme.getId(),loadDays);
                }
            }else{
                // 本艘船能够装载的货量（百万英热），排货默认满载
                BigDecimal mut = loadShipInfo.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                        .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                        .setScale(0, BigDecimal.ROUND_DOWN);

                if (goodsQuantity != null) {
                    if(allGoodsQuantity.compareTo(mut) > 0){
                        if(goodsQuantity.compareTo(mut) < 0){
                            //如果剩余货量达不到当前船只满载，则舍去这船货
                            goodsQuantity = BigDecimal.ZERO;
                            break;
                        }
                    }else{
                        //输入货量小于舱容，则说明只拉一船货，装载量=货量
                        mut = goodsQuantity;
                    }
                    goodsQuantity = goodsQuantity.subtract(mut);
                }
                schemeDetail.setStorageCapacity(loadShipInfo.getStorageCapacity());
                schemeDetail.setShipName(loadShipInfo.getNameOfVessel());
                schemeDetail.setImo(loadShipInfo.getImo());
                schemeDetail.setSingleGoodsQuantity(mut);
                schemeDetail.setOilConsumption(loadShipInfo.getOilConsumption());
                getDispatchIntervalDaysAndTradeRate(schemeDetail,loadShipInfo,resourceMemorandum);

                insertData.add(schemeDetail);
            }
            if(insertData.size() == 0){
                return JsonResult.failed("当前方案无符合的船只或无可用时间空挡！");
            }

            for (SpDispatchPotentialSchemeDetail insertDetail : insertData) {
                this.baseMapper.insert(insertDetail);
                updateShipSchedule(insertDetail);
            }

            //获取本次排货最开始的装载时间
            SpDispatchPotentialSchemeDetail detailFirst = insertData.stream().min(Comparator.comparing((SpDispatchPotentialSchemeDetail::getExpectedLoadingDate))).get();
            Date resetStartDate = detailFirst.getExpectedLoadingDate();

            //重新生成航次号和货号
            List<SpDispatchPotentialSchemeDetail> detailList = this.baseMapper.selectList(
                    new LambdaQueryWrapper<SpDispatchPotentialSchemeDetail>().eq(SpDispatchPotentialSchemeDetail::getSchemeId, spDispatchScheme.getId()));
            if (detailList.size() > 0) {
                detailList = CommonUtil.resetVoyageNoAndGoodsNumberPotential(detailList);

                detailList = resetSetLastEmptyInfo(detailList,resetStartDate);

                //重新计算一下前后空挡信息
                detailList = getIntervalDays(detailList,null);

                //计算优化成本及船舶利用率
                detailList = getUseRateAndDispatchCost(detailList,null);
                this.updateBatchById(detailList);
            }
        }
        return JsonResult.success(tipString);
    }

    /**
     * 获取船货前后空挡天数
     * @param detailAllList
     * @return
     */
    public List<SpDispatchPotentialSchemeDetail> getUseRateAndDispatchCost(List<SpDispatchPotentialSchemeDetail> detailAllList, Integer year){
        if(year == null){
            LocalDate currentDate = LocalDate.now();
            year = currentDate.getYear();
        }

        List<SpInformation> spInformations = spInformationService.getAllSpInformationUsed(null);
        if(spInformations.size() == 0){
            return detailAllList;
        }
        if(detailAllList.size() == 0){
            return detailAllList;
        }
        Map<String,List<SpInformation>> shipMap = spInformations.stream().collect(Collectors.groupingBy(SpInformation::getImo));
        Map<String,List<SpDispatchPotentialSchemeDetail>> schemeDetailMap = detailAllList.stream().collect(Collectors.groupingBy(SpDispatchPotentialSchemeDetail::getImo));

        List<SpDispatchPotentialSchemeDetail> resultList = new LinkedList<>();
        for (String s : schemeDetailMap.keySet()) {
            List<SpDispatchPotentialSchemeDetail> detailList = schemeDetailMap.get(s);
            if(shipMap.get(s) == null){
                resultList.addAll(detailList);
                continue;
            }
            SpInformation spInformation = shipMap.get(s).get(0);
            BigDecimal yearInRentDays = BigDecimal.ZERO;
            BigDecimal yearInRentMoney = BigDecimal.ZERO;
            BigDecimal yearOutRentDays = BigDecimal.ZERO;
            BigDecimal yearOutRentMoney = BigDecimal.ZERO;

            ShipRentYearInfoBO yearInfoBO = CommonUtil.getShipYearTradeInfo(spInformation,year);
            yearInRentDays = yearInRentDays.add(yearInfoBO.getYearInRentDays());
            yearInRentMoney = yearInRentMoney.add(yearInfoBO.getYearInRentMoney());
            yearOutRentDays = yearOutRentDays.add(yearInfoBO.getYearOutRentDays());
            yearOutRentMoney = yearOutRentMoney.add(yearInfoBO.getYearOutRentMoney());

            BigDecimal shipDays = BigDecimal.ZERO;
            for (SpDispatchPotentialSchemeDetail schemeDetail : detailList) {
                if(schemeDetail.getPlanYear() != null && schemeDetail.getPlanYear().intValue() != year){
                    continue;
                }
                BigDecimal currentDay = BigDecimal.ZERO;
                if(schemeDetail.getExpectedFullLoadVoyageDays() != null){
                    currentDay = currentDay.add(new BigDecimal(String.valueOf(schemeDetail.getExpectedFullLoadVoyageDays())));
                }
                if(schemeDetail.getExpectedEmptyLoadVoyageDays() != null){
                    currentDay = currentDay.add(new BigDecimal(String.valueOf(schemeDetail.getExpectedEmptyLoadVoyageDays())));
                }
                shipDays = shipDays.add(currentDay);

                if(yearInRentDays.compareTo(BigDecimal.ZERO) != 0) {
                    schemeDetail.setShipUseRate(currentDay.divide(yearInRentDays, 3, RoundingMode.HALF_UP).multiply(new BigDecimal("100")));
                }
            }

            //计算船舶优化成本=外租收益-运力成本
            //外租收益=所有租出船舶租金*该船舶本年度合同租出天数之和
            //运力成本=所有船舶的租金*年度租的天数-租出的船舶天数*所有船舶租出的租金
            BigDecimal dispatchCost = yearOutRentMoney.subtract(yearInRentMoney.subtract(yearOutRentMoney));
            dispatchCost = dispatchCost.divide(new BigDecimal("10000"),0,RoundingMode.UP);
            for (SpDispatchPotentialSchemeDetail schemeDetail : detailList) {
                if(schemeDetail.getPlanYear() != null && schemeDetail.getPlanYear().intValue() != year){
                    continue;
                }
                schemeDetail.setDispatchCost(dispatchCost);
            }
            resultList.addAll(detailList);
        }
        return resultList;
    }

    @Override
    public JsonResult schemeChartCalculateSave(DispatchPotentialSchemeChartSaveDto dto) {
        List<SpDispatchPotentialSchemeDetail> details = dto.getSchemeSaveData();
        if(details.size() == 0){
            return JsonResult.failed("保存的方案信息不能为空");
        }

        //校验每艘船设置的货运时间是否有冲突
        Map<String,List<SpDispatchPotentialSchemeDetail>> boatMap = details.stream().collect(Collectors.groupingBy(SpDispatchPotentialSchemeDetail::getShipName));
        for (String boatName : boatMap.keySet()) {
            List<SpDispatchPotentialSchemeDetail> boatSchemeDetailList = boatMap.get(boatName);
            //先根据装载时间排序，然后遍历判断，如果当前数据的装载时间时间在上一条数据的返程时间之前，则表示时间窗口有冲突
            boatSchemeDetailList = boatSchemeDetailList.stream().sorted(Comparator.comparing(SpDispatchPotentialSchemeDetail::getExpectedLoadingDate)).collect(Collectors.toList());
            Date compareReturnDate = null;
            for (SpDispatchPotentialSchemeDetail detail : boatSchemeDetailList) {
                if(detail.getIsFobSell() != null && detail.getIsFobSell().intValue() == 1){
                    continue;
                }

                if(compareReturnDate != null && detail.getExpectedLoadingDate().before(compareReturnDate)){
                    return JsonResult.failed("时间窗口冲突【船名："+boatName+",装载时间："+detail.getExpectedLoadingDate()+"】");
                }
                compareReturnDate = detail.getExpectedEmptyReturnWindow();
            }
        }

        //获取原始方案数据
        SpDispatchPotentialScheme spDispatchScheme = spDispatchService.getBaseMapper().selectById(dto.getSchemeId());
        if(spDispatchScheme == null){
            return JsonResult.failed("方案信息异常");
        }

//        if(spDispatchScheme.getIsShare().intValue() == 1 ){
//            //如果当前方案是已经共享了的，需要新增一条方案信息作为个人的，不能影响已经共享出去的方案
//            spDispatchScheme.setId(null);
//            spDispatchScheme.setIsHistory(0);
//            spDispatchScheme.setIsShare(0);
//            spDispatchScheme.setApproveStatus(1);
//            spDispatchScheme.setCreateDate(new Date());
//            spDispatchScheme.setCreateBy(SecurityUtils.getUsername());
//            spDispatchService.getBaseMapper().insert(spDispatchScheme);
//        }

        //重新生成航次号和货号
        details = CommonUtil.resetVoyageNoAndGoodsNumberPotential(details);
        for (int i = 1; i <= details.size(); i++) {
            SpDispatchPotentialSchemeDetail detail = details.get(i-1);
            detail.setSchemeId(spDispatchScheme.getId());
            detail.setCreateDate(new Date());
            detail.setCreateBy(SecurityUtils.getUsername());
            SpInformation spInformation = spInformationService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpInformation>()
                            .eq(SpInformation::getNameOfVessel,detail.getShipName())
                            .last("limit 1")
            );
            if(spInformation != null && !StringUtils.equals(spInformation.getImo(),detail.getImo())){
                //处理船只imo不匹配的情况
                detail.setImo(spInformation.getImo());
            }
            getDispatchIntervalDaysAndTradeRate(detail,spInformation, null);
        }

        if(details.size() > 0){

            this.updateBatchById(details);

            //重新计算空载时间
            details = resetSetLastEmptyInfo(details,new Date());

            //重新计算一下前后空挡信息
            details = getIntervalDays(details,LocalDate.now().getYear());

            //计算优化成本及船舶利用率
            details = getUseRateAndDispatchCost(details,null);
            this.updateBatchById(details);
        }
        return JsonResult.success();
    }

    @Override
    public JsonResult schemeChartSave(DispatchPotentialSchemeChartSaveDto dto) {
        List<SpDispatchPotentialSchemeDetail> details = dto.getSchemeSaveData();
        if(details.size() == 0){
            return JsonResult.failed("保存的方案信息不能为空");
        }

        //校验每艘船设置的货运时间是否有冲突
        Map<String,List<SpDispatchPotentialSchemeDetail>> boatMap = details.stream().collect(Collectors.groupingBy(SpDispatchPotentialSchemeDetail::getShipName));
        for (String boatName : boatMap.keySet()) {
            List<SpDispatchPotentialSchemeDetail> boatSchemeDetailList = boatMap.get(boatName);
            //先根据装载时间排序，然后遍历判断，如果当前数据的装载时间时间在上一条数据的返程时间之前，则表示时间窗口有冲突
            boatSchemeDetailList = boatSchemeDetailList.stream().sorted(Comparator.comparing(SpDispatchPotentialSchemeDetail::getExpectedEmptyReturnWindow)).collect(Collectors.toList());
            Date compareReturnDate = null;
            for (SpDispatchPotentialSchemeDetail detail : boatSchemeDetailList) {
                if(detail.getIsFobSell() != null && detail.getIsFobSell().intValue() == 1){
                    continue;
                }

                if(compareReturnDate != null && detail.getExpectedLoadingDate().before(compareReturnDate)){
                    return JsonResult.failed("时间窗口冲突【船名："+boatName+",装载时间："+detail.getExpectedLoadingDate()+"】");
                }
                compareReturnDate = detail.getExpectedEmptyReturnWindow();
            }
        }

        //获取原始方案数据
        SpDispatchPotentialScheme spDispatchScheme = spDispatchService.getBaseMapper().selectById(dto.getSchemeId());
        if(spDispatchScheme == null){
            return JsonResult.failed("方案信息异常");
        }
//        if(spDispatchScheme.getIsDistribute() != null && spDispatchScheme.getIsDistribute().intValue() == 1){
//            return JsonResult.failed("当前方案已下发,操作失败!");
//        }

        //添加当前方案现有的fob销售类型的货物
        List<SpDispatchPotentialSchemeDetail> fobSellList = this.baseMapper.selectList(
                new LambdaQueryWrapper<SpDispatchPotentialSchemeDetail>()
                        .eq(SpDispatchPotentialSchemeDetail::getIsFobSell,1)
                        .eq(SpDispatchPotentialSchemeDetail::getSchemeId,dto.getSchemeId())
        );
        if(fobSellList.size() > 0){
            details.addAll(fobSellList);
        }

        Integer number = null;
        if(dto.getSaveType().intValue() != 1){
            //如果不是个人草稿,则将之前的所有记录都设置为历史记录,当前共享的数据为最新的一条
            SpDispatchPotentialScheme updateData = new SpDispatchPotentialScheme();
            updateData.setIsHistory(1);
            spDispatchService.getBaseMapper().update(updateData,new LambdaQueryWrapper<>());

//            SpYearPlanScheme maxScheme = spYearlyPlanMapper.selectMaxNumber(SecurityUtils.getStringUserId(), spYearPlanScheme.getPlanId());
            SpDispatchPotentialScheme maxScheme = spDispatchService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpDispatchPotentialScheme>()
                            .eq(SpDispatchPotentialScheme::getIsShare,1)
                            .orderByDesc(SpDispatchPotentialScheme::getNumber)
                            .last("limit 1")
            );
            number = 1;
            if (maxScheme != null) {
                number = (maxScheme.getNumber() + 1);
            }
        }

        //新增一条方案信息
        spDispatchScheme.setId(null);
        spDispatchScheme.setIsHistory(0);
        spDispatchScheme.setIsShare(0);
        spDispatchScheme.setNumber(number);

        if(dto.getSaveType().intValue() == 3){
            spDispatchScheme.setIsDistribute(1);
        }
        spDispatchScheme.setApproveStatus(1);
        if(dto.getSaveType().intValue() != 1){
            spDispatchScheme.setIsShare(1);
        }
        spDispatchScheme.setCreateDate(new Date());
        spDispatchScheme.setCreateBy(SecurityUtils.getUsername());
        spDispatchService.getBaseMapper().insert(spDispatchScheme);

        //重新生成航次号和货号
        details = CommonUtil.resetVoyageNoAndGoodsNumberPotential(details);
        for (int i = 1; i <= details.size(); i++) {
            SpDispatchPotentialSchemeDetail detail = details.get(i-1);
            Long id = detail.getId();
            detail.setSchemeId(spDispatchScheme.getId());
            detail.setCreateDate(new Date());
            detail.setLastDetailId(id);
            detail.setCreateBy(SecurityUtils.getUsername());
            detail.setId(null);
            SpInformation spInformation = spInformationService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpInformation>()
                            .eq(SpInformation::getNameOfVessel,detail.getShipName())
                            .last("limit 1")
            );
            if(spInformation != null && !StringUtils.equals(spInformation.getImo(),detail.getImo())){
                //处理船只imo不匹配的情况
                detail.setImo(spInformation.getImo());
            }

            getDispatchIntervalDaysAndTradeRate(detail,spInformation,null);
        }
        if(details.size() > 0){

            this.saveBatch(details);

            details = resetSetLastEmptyInfo(details,new Date());

            //重新计算一下前后空挡信息
            details = getIntervalDays(details,LocalDate.now().getYear());

            //计算优化成本及船舶利用率
            details = getUseRateAndDispatchCost(details,null);
            this.updateBatchById(details);
        }

        return JsonResult.success();
    }

    /**
     * 重新计算上一航次信息
     * @param detailList
     * @return
     */
    public List<SpDispatchPotentialSchemeDetail> resetSetLastEmptyInfo(List<SpDispatchPotentialSchemeDetail> detailList, Date resetStartDate){
        for (SpDispatchPotentialSchemeDetail schemeDetail : detailList) {
            //只需要对第一个优化时间之后的货物进行空挡重新计算
            if(resetStartDate != null && schemeDetail.getExpectedLoadingDate().before(resetStartDate)){
                continue;
            }

            //根据船只上一航次卸载港-当前航次装载港计算空载时间
            //预计空载航程天数
            SpDispatchPotentialSchemeDetail lastSchemeDetail = this.baseMapper.selectOne(
                    new LambdaQueryWrapper<SpDispatchPotentialSchemeDetail>()
                            .lt(SpDispatchPotentialSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                            .eq(SpDispatchPotentialSchemeDetail::getImo,schemeDetail.getImo())
                            .eq(SpDispatchPotentialSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                            .orderByDesc(SpDispatchPotentialSchemeDetail::getExpectedLoadingDate)
                            .last("limit 1")
            );
            schemeDetail.setLastShipSpeed(schemeDetail.getShipSpeed());
            if(lastSchemeDetail == null){
                //没有上一船货，则没有空载时间
                schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());

                //没有空载时间则清空空载航线信息
                schemeDetail.setEmptyDistance(0);
                schemeDetail.setLastUnloadPortName("");
            }else{
                ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),lastSchemeDetail.getExpectedUnloadThirdId()
                        ,schemeDetail.getExpectedLoadThirdId(),null,null);
                if(routeDay != null){
                    int day = routeDay.getShipRouteDay().intValue();
                    schemeDetail.setEmptyDistance(routeDay.getTrackDistance());
                    schemeDetail.setLastUnloadPortName(lastSchemeDetail.getExpectedUnloadingPort());
                    schemeDetail.setExpectedEmptyLoadVoyageDays(day);
                    schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~day + 1));
                }else{
                    schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                    schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());
                }
            }
        }
        return detailList;
    }

    /**
     * 获取船只租期
     * @param imo
     * @return
     */
    public ConShipping getShipRentTime(String imo){
        ConShipping conShipping = conShippingMapper.selectOne(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,imo)
                        .in(ConShipping::getStatus, 1, 2, 3)
                        .orderByDesc(ConShipping::getDeliveryDateBegin)
                        .last("limit 1")
        );
        return conShipping;
    }

    /**
     * 获取船货前后空挡天数
     * @return
     */
    public List<SpDispatchPotentialSchemeDetail> getIntervalDays(List<SpDispatchPotentialSchemeDetail> detailListAll,Integer year){
        if(detailListAll.size() == 0){
            return detailListAll;
        }
        List<SpDispatchPotentialSchemeDetail> saveList = new LinkedList<>();
        Map<Integer,List<SpDispatchPotentialSchemeDetail>> yearMap = detailListAll.stream().collect(Collectors.groupingBy(SpDispatchPotentialSchemeDetail::getPlanYear));
        for (Integer integer : yearMap.keySet()) {
            List<SpDispatchPotentialSchemeDetail> detailListYear = yearMap.get(integer);
            year = integer;

            Date yearBegin = DateUtil.parse(year + "-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss");
            Date yearEnd = DateUtil.parse(year + "-12-31 23:59:59", "yyyy-MM-dd HH:mm:ss");

            Map<String,List<SpDispatchPotentialSchemeDetail>> detailMap = detailListYear.stream().collect(Collectors.groupingBy(SpDispatchPotentialSchemeDetail::getImo));
            for (String s : detailMap.keySet()) {
                List<SpDispatchPotentialSchemeDetail> detailList = detailMap.get(s);
//                ConShipping conShipping = getShipRentTime(s);
//                if(conShipping != null){
//                    if(conShipping.getDeliveryDateBegin().after(yearBegin) && conShipping.getDeliveryDateBegin().before(yearEnd)){
//                        yearBegin = conShipping.getDeliveryDateBegin();
//                    }
//                    if(conShipping.getRedeliveryDateEnd().before(yearEnd) && conShipping.getRedeliveryDateEnd().after(yearBegin)){
//                        yearEnd = conShipping.getRedeliveryDateEnd();
//                    }
//                }
                if(detailList.size() == 1){
                    Integer frontDays = 0;
                    Integer backDays = 0;
                    Integer emptyDays = 0;
                    SpDispatchPotentialSchemeDetail schemeDetail = detailList.get(0);
                    if(schemeDetail.getExpectedEmptyReturnWindow().after(yearBegin)){
                        frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(yearBegin,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                    }
                    if(schemeDetail.getExpectedUnloadingWindow().before(yearEnd)){
                        backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow(),yearEnd,true)));
                    }
                    schemeDetail.setFrontDays(frontDays);
                    schemeDetail.setBackDays(backDays);
                    if(frontDays >= 30){
                        emptyDays = (emptyDays+frontDays);
                    }
                    if(backDays >= 30){
                        emptyDays = (emptyDays+backDays);
                    }
                    schemeDetail.setEmptyDays(emptyDays);
                    schemeDetail.setUnoccupiedDays(emptyDays);
                    saveList.addAll(detailList);
                    continue;
                }
                detailList = detailList.stream().sorted(Comparator.comparing(SpDispatchPotentialSchemeDetail::getExpectedEmptyReturnWindow))
                        .collect(Collectors.toList());

                Date lastEndDate = yearBegin;
                for (int i = 0; i < detailList.size(); i++) {
                    SpDispatchPotentialSchemeDetail schemeDetail = detailList.get(i);
                    Integer frontDays = 0;
                    Integer backDays = 0;
                    Integer emptyDays = 0;
                    if(i == (detailList.size()-1)){
                        //最后一条
                        frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(lastEndDate,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                        if(schemeDetail.getExpectedUnloadingWindow().before(yearEnd)){
                            backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow(),yearEnd,true)));
                        }
                        schemeDetail.setFrontDays(frontDays);
                        schemeDetail.setBackDays(backDays);
                        if(frontDays >= 30){
                            emptyDays = (emptyDays+frontDays);
                        }
                        if(backDays >= 30){
                            emptyDays = (emptyDays+backDays);
                        }
                        schemeDetail.setEmptyDays(emptyDays);
                        schemeDetail.setUnoccupiedDays(emptyDays);
                        continue;
                    }

                    SpDispatchPotentialSchemeDetail nextSchemeDetail = detailList.get(i+1);

                    if(i == 0){
                        //第一条
                        if(schemeDetail.getExpectedEmptyReturnWindow().after(yearBegin)){
                            frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(yearBegin,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                        }
                        backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow()
                                ,nextSchemeDetail.getExpectedEmptyReturnWindow(),true)));
                        schemeDetail.setFrontDays(frontDays);
                        schemeDetail.setBackDays(backDays);
                        if(frontDays >= 30){
                            emptyDays = (emptyDays+frontDays);
                        }
                        if(backDays >= 30){
                            emptyDays = (emptyDays+backDays);
                        }

                        lastEndDate = schemeDetail.getExpectedUnloadingWindow();
                        schemeDetail.setEmptyDays(emptyDays);
                        schemeDetail.setUnoccupiedDays(emptyDays);
                        continue;
                    }
                    frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(lastEndDate,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                    backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow()
                            ,nextSchemeDetail.getExpectedEmptyReturnWindow(),true)));
                    schemeDetail.setFrontDays(frontDays);
                    schemeDetail.setBackDays(backDays);
                    if(frontDays >= 30){
                        emptyDays = (emptyDays+frontDays);
                    }
                    if(backDays >= 30){
                        emptyDays = (emptyDays+backDays);
                    }
                    schemeDetail.setEmptyDays(emptyDays);
                    schemeDetail.setUnoccupiedDays(emptyDays);
                    lastEndDate = schemeDetail.getExpectedUnloadingWindow();
                }
                saveList.addAll(detailList);
            }
        }
        return saveList;
    }

    @Override
    public JsonResult dataUpdate() {
        //最新的调度方案数据
        SpDispatchScheme spDispatchSchemes = dispatchService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpDispatchScheme>()
                        .eq(SpDispatchScheme::getIsHistory,0)
                        .and(qw -> {
                            qw.eq(SpDispatchScheme::getCreateBy,SecurityUtils.getUsername());
                            qw.or().eq(SpDispatchScheme::getIsShare,1);
                        })
                        .orderByDesc(SpDispatchScheme::getCreateDate)
                        .last("limit 1")
        );
        if(spDispatchSchemes == null){
            return JsonResult.success();
        }
        List<SpDispatchSchemeDetail> details = dispatchSchemeDetailService.getBaseMapper().selectList(
                new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                        .eq(SpDispatchSchemeDetail::getSchemeId,spDispatchSchemes.getId())
                        .ne(SpDispatchSchemeDetail::getExecuteStatus,4)
        );

        //最新的潜在投标信息
        SpDispatchPotentialScheme potentialScheme = spDispatchService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpDispatchPotentialScheme>()
                        .eq(SpDispatchPotentialScheme::getIsHistory,0)
                        .and(qw -> {
                            qw.eq(SpDispatchPotentialScheme::getCreateBy,SecurityUtils.getUsername());
                            qw.or().eq(SpDispatchPotentialScheme::getIsShare,1);
                        })
                        .orderByDesc(SpDispatchPotentialScheme::getCreateDate)
                        .last("limit 1")
        );
        if(potentialScheme != null){
            potentialScheme.setIsHistory(1);
            spDispatchService.getBaseMapper().updateById(potentialScheme);
        }

        //新增一条方案信息
        SpDispatchPotentialScheme newScheme = new SpDispatchPotentialScheme();
        newScheme.setId(IDUtil.nextId());
        newScheme.setCreateDate(new Date());
        newScheme.setCreateBy(SecurityUtils.getUsername());
        newScheme.setNumber(1);
        newScheme.setIsShare(1);
        newScheme.setIsHistory(0);
        newScheme.setIsDistribute(1);
        spDispatchService.save(newScheme);

        List<SpDispatchPotentialSchemeDetail> saveList = new LinkedList<>();
        for (SpDispatchSchemeDetail detail : details) {
            SpDispatchPotentialSchemeDetail saveData = new SpDispatchPotentialSchemeDetail();
            BeanCopyUtils.copy(detail,saveData);
            saveData.setId(null);
            saveData.setSchemeId(newScheme.getId());
            saveList.add(saveData);
        }
        if(saveList.size() > 0){
            this.saveBatch(saveList);
        }

        potentialScheduleService.getBaseMapper().delete(new LambdaQueryWrapper<>());

        //同步船期表信息
        List<SpShipSchedule> schedules = scheduleService.getBaseMapper().selectList(new LambdaQueryWrapper<>());
        List<SpShipPotentialSchedule> potentialScheduleList = new LinkedList<>();
        for (SpShipSchedule schedule : schedules) {
            SpShipPotentialSchedule potentialSchedule = new SpShipPotentialSchedule();
            BeanCopyUtils.copy(schedule,potentialSchedule);
            potentialScheduleList.add(potentialSchedule);
        }
        if(potentialScheduleList.size() > 0){
            potentialScheduleService.saveBatch(potentialScheduleList);
        }

        return JsonResult.success();
    }

    /**
     * 获取贸易利润率及空挡天数
     * @param schemeDetail
     * @param shipInfo
     */
    private void getDispatchIntervalDaysAndTradeRate(SpDispatchPotentialSchemeDetail schemeDetail
            ,SpInformation shipInfo,ConResourceMemorandum memorandum){

        if(memorandum == null && schemeDetail.getResourceContract() != null){
            memorandum = resourceMemorandumMapper.selectById(schemeDetail.getResourceContract());
        }
        //实际交付量 = 预计装载量-预计装载量*（时间差天数）*蒸发率-留底量 如果是虚拟船就默认等于预计装载量
        BigDecimal realityUnload = schemeDetail.getSingleGoodsQuantity();
        //蒸发率
        schemeDetail.setEvaporationRate(shipInfo.getEvaporationRate() != null ? shipInfo.getEvaporationRate() : new BigDecimal("0.1"));
        BigDecimal subDec = BigDecimal.ZERO;
        if(shipInfo.getEvaporationRate() != null){
            subDec = schemeDetail.getSingleGoodsQuantity().multiply(schemeDetail.getEvaporationRate()).divide(new BigDecimal("100"));
        }
        //蒸发量
        schemeDetail.setExpectedEvaporationQuantity(subDec);
        realityUnload = schemeDetail.getSingleGoodsQuantity().subtract(subDec);

        //预计卸载量
        schemeDetail.setExpectedUnloadGoodsQuantity(realityUnload);
        //实际卸载量
        schemeDetail.setRealityUnloadingCapacity(realityUnload);

        //涉及成本计算的字段单位都是万美元
        BigDecimal defUnitValue = new BigDecimal("10000");
        BigDecimal defZeroValue = BigDecimal.ZERO;

        schemeDetail.setLoadPassCost(defZeroValue);
        schemeDetail.setLoadPortCost(defZeroValue);
        schemeDetail.setLoadOtherCost(defZeroValue);
        schemeDetail.setUnloadPassCost(defZeroValue);
        schemeDetail.setUnloadPortCost(defZeroValue);
        schemeDetail.setUnloadOtherCost(defZeroValue);
        schemeDetail.setExpectedBottomQuantity(defZeroValue);

        //实际交付价格 = 合同价格
        if(memorandum != null && memorandum.getContractPrice() != null){
            //预计采购价格
            schemeDetail.setConPrice(memorandum.getContractPrice());
            //预计采购成本 计划装载量*预计采购单价
            schemeDetail.setConPriceCost(schemeDetail.getConPrice().multiply(schemeDetail.getSingleGoodsQuantity()).divide(defUnitValue,2,RoundingMode.UP));
            //实际采购单价 默认为预计采购单价
            schemeDetail.setRealityConPrice(schemeDetail.getConPrice());
            //实际采购成本 实际装载量*实际采购单价
            BigDecimal realityConPriceCost = schemeDetail.getRealityConPrice().multiply(schemeDetail.getSingleGoodsQuantity());
            schemeDetail.setRealityConPriceCost(realityConPriceCost.divide(defUnitValue,2,RoundingMode.UP));
            //实际总装载成本=运河费（装载）+港口费（装载）+其他费用（装载）+实际采购成本
            schemeDetail.setRealityLoadTotalCost((realityConPriceCost.add(schemeDetail.getLoadPassCost())
                    .add(schemeDetail.getLoadPortCost()).add(schemeDetail.getLoadOtherCost())).divide(defUnitValue,2,RoundingMode.UP));
        }else{
            schemeDetail.setConPrice(defZeroValue);
            //预计采购成本 计划装载量*预计采购单价
            schemeDetail.setConPriceCost(defZeroValue);
            //实际采购单价 默认为预计采购单价
            schemeDetail.setRealityConPrice(defZeroValue);
            //实际采购成本 实际装载量*实际采购单价
            schemeDetail.setRealityConPriceCost(defZeroValue);
            //实际总装载成本=运河费（装载）+港口费（装载）+其他费用（装载）+实际采购成本
            schemeDetail.setRealityLoadTotalCost(defZeroValue);
        }

        /**
         * 年度计划默认都是回国，根据贸易利润为0来推算国内销售价格
         * 回国：贸易利润 = 实际交付量（预计卸载量）*实际交付价格（实际销售的价格）
         * +其他收入（为0）-其他交付成本（为0）-实际装载量（预计装载量）*实际结算单价（长协合同里面的合同价格）-其他费用（为0）
         */

        //国际贸易利润
        schemeDetail.setNationalTradingProfit(defZeroValue);
        BigDecimal sellPrice = BigDecimal.ZERO;
        if(schemeDetail.getConPrice().compareTo(BigDecimal.ZERO) > 0){
            sellPrice = (schemeDetail.getSingleGoodsQuantity().multiply(schemeDetail.getConPrice()))
                    .divide(schemeDetail.getRealityUnloadingCapacity(),2,RoundingMode.HALF_UP);
        }
        //预计销售价格
        schemeDetail.setSellPrice(sellPrice);
        //预计销售收入
        schemeDetail.setExpectSellMoney(schemeDetail.getSellPrice().multiply(schemeDetail.getExpectedUnloadGoodsQuantity()).divide(defUnitValue,2,RoundingMode.UP));
        //实际销售价格
        schemeDetail.setRealitySellPrice(sellPrice);
        //预计销售收入
        schemeDetail.setRealitySellMoney(schemeDetail.getRealitySellPrice().multiply(schemeDetail.getRealityUnloadingCapacity()).divide(defUnitValue,2,RoundingMode.UP));
        //其他收入
        schemeDetail.setOtherGetMoney(defZeroValue);
        //补货利润
        schemeDetail.setRestockProfit(defZeroValue);
        //船舶优化成本
        schemeDetail.setDispatchCost(defZeroValue);
        //船舶利用率
        schemeDetail.setShipUseRate(defZeroValue);
        //可优化天数
        schemeDetail.setUnoccupiedDays(0);
        //实际总交付成本=运河费（卸载）+港口费（卸载）+其他费用（卸载)
        schemeDetail.setRealityTotalDeliverCost((schemeDetail.getUnloadPassCost()
                .add(schemeDetail.getUnloadPortCost()).add(schemeDetail.getUnloadOtherCost())).divide(defUnitValue,2,RoundingMode.UP));
        //贸易利润率=国际贸易利润+补货利润+船舶优化成本
        schemeDetail.setTradingProfit(schemeDetail.getNationalTradingProfit().add(schemeDetail.getRestockProfit()).add(schemeDetail.getDispatchCost()));
    }

    /**
     * 虚拟船排货逻辑
     * @param virtualShips
     * @param schemeModel
     * @param goodsQuantity
     * @param planId
     * @param year
     * @param resource
     * @param schemeId
     */
    public List<SpDispatchPotentialSchemeDetail> virtualShipDispatch(List<SpInformation> virtualShips,SpDispatchSchemeDetailModel schemeModel
            ,BigDecimal goodsQuantity,Long planId, int year, ConResourceMemorandum resource
            , Long schemeId,ShipLoadDaysAndRouteVo loadDays){
        List<SpDispatchPotentialSchemeDetail> insertData = new LinkedList<>();
        BigDecimal virGoodsQuantity = goodsQuantity;
        Date loadDate = schemeModel.getExpectedLoadingDate();
        if (virGoodsQuantity != null) {
            if(virGoodsQuantity.compareTo(BigDecimal.ZERO) <= 0){
                return insertData;
            }
        }

        //根据装载时间初步生成船货信息
        SpDispatchPotentialSchemeDetail schemeDetail = initDispatchSchemeDetail(year,resource,schemeId,loadDate,schemeModel,loadDays);
        schemeDetail.setPlanId(planId);

        SpInformation virtualShip = null;
        for (SpInformation spInformation : virtualShips) {
            if(checkShipTimeWindows(spInformation,schemeDetail)){
                virtualShip = spInformation;
                break;
            }
        }
        if(virtualShip == null){
            return insertData;
        }

        // 本艘船能够装载的货量（百万英热），排货默认满载
        BigDecimal mut = virtualShip.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                .setScale(0, BigDecimal.ROUND_DOWN);
        schemeDetail.setShipName(virtualShip.getNameOfVessel());
        schemeDetail.setImo(virtualShip.getImo());
        schemeDetail.setSingleGoodsQuantity(mut);
        schemeDetail.setOilConsumption(virtualShip.getOilConsumption());

        getDispatchIntervalDaysAndTradeRate(schemeDetail,virtualShip,null);
        schemeDetail.setStorageCapacity(virtualShip.getStorageCapacity());
        insertData.add(schemeDetail);
        return insertData;
    }

    /**
     * 更新船期表
     * @param schemeDetail
     */
    public void updateShipSchedule(SpDispatchPotentialSchemeDetail schemeDetail){
        if(schemeDetail.getImo().indexOf("virtualShip") > -1){
            SpInformation spInformation = spInformationService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpInformation>()
                    .eq(SpInformation::getImo,schemeDetail.getImo())
                    .last("limit 1")
            );
            if(spInformation != null && StringUtils.isBlank(spInformation.getRentTimeSlot())){
                //虚拟船租金信息为空，说明是刚新增的，需要根据装卸载时间来进行租期设置
                Date startDate = schemeDetail.getExpectedLoadingDate();
                Date endDate = schemeDetail.getExpectedUnloadingWindow();

                Calendar calendar1 = Calendar.getInstance();
                calendar1.setTime(startDate);
                Calendar calendar2 = Calendar.getInstance();
                calendar2.setTime(endDate);
                if(calendar1.get(Calendar.YEAR) != calendar2.get(Calendar.YEAR)){
                    //说明跨年了，租期结束时间得推到卸载时间得年末
                    calendar2.set(Calendar.MONTH, Calendar.DECEMBER);
                    calendar2.set(Calendar.DAY_OF_MONTH, 31);
                    endDate = calendar2.getTime();
                }

                //组装租期、租金信息
                JSONArray array = new JSONArray();
                JSONObject json = new JSONObject();
                json.put("rentDateBegin",startDate);
                json.put("rentDateEnd",endDate);
                json.put("rentParagraph",6000);
                json.put("rentUnit",1);
                array.add(json);
                spInformation.setRentTimeSlot(array.toJSONString());
                spInformationService.getBaseMapper().updateById(spInformation);
            }
        }

        //使用这个变量来判断这艘船是否再租出时间范围内，如果再就为true
        AtomicReference<Boolean> judge = new AtomicReference<>(false);
        //起租开始时间
        AtomicReference<Date> startingDateOfLeaseBegin = new AtomicReference<>();
        //还船结束时间
        AtomicReference<Date> redeliveryDateEnd = new AtomicReference<>();
        //租出时间，多条
        List<String> leaseTermStrs = new ArrayList<>();
        //从船运合同中获取信息
        List<ConShipping> conShippings = conShippingMapper.getByIMO(schemeDetail.getImo());
        if (conShippings != null && conShippings.size() > 0) {
            conShippings.stream().forEach(c -> {
                if (c.getContractType() != null && c.getContractType().equals(3)) {
                    //租出
                    leaseTermStrs.add(DateUtil.format(c.getDeliveryDateBegin(), "yyyy-MM-dd HH:mm:ss") + "," + DateUtil.format(c.getDeliveryDateEnd(), "yyyy-MM-dd HH:mm:ss"));
                    //空载时间小于交船时间
                    if (c.getDeliveryDateBegin()!=null && !schemeDetail.getExpectedEmptyReturnWindow().before(c.getDeliveryDateBegin())) {
                        judge.set(true);
                        //装载时间大于还船时间
                        if (c.getDeliveryDateEnd()!=null && !schemeDetail.getExpectedLoadingDate().after(c.getDeliveryDateEnd())) {
                            judge.set(true);
                        }else{
                            judge.set(false);
                        }
                    }
                } else {
                    schemeDetail.setShipContractId(c.getId());
                    schemeDetail.setShipContractName(c.getContractName());
                    schemeDetail.setShipTradeBody(c.getTradeBody());
                    schemeDetail.setShipContractType(c.getContractType());
                    if (c.getDeliveryDateBegin() != null) {
                        startingDateOfLeaseBegin.set(c.getDeliveryDateBegin());
                    }
                    redeliveryDateEnd.set(c.getRedeliveryDateEnd());
                }
                //off-hire状态，时间段不能生成货数.并且记录在船期表中
                if(c.getStatus()!=null && c.getStatus().equals(5)){
                    if(StringUtils.isNotBlank(c.getHireTimeValue())){
                        List<HireTimeDetailsBO> hireTimeDetailsBOS = new ArrayList<>();
                        ObjectMapper objectMapper = new ObjectMapper();
                        try {
                            hireTimeDetailsBOS = objectMapper.readValue(c.getHireTimeValue(), new TypeReference<List<HireTimeDetailsBO>>() {
                            });
                            if(hireTimeDetailsBOS!=null && hireTimeDetailsBOS.size()>0){
                                hireTimeDetailsBOS.stream().forEach(h->{
                                    //空载时间小于交船时间
                                    if (h.getStart()!=null && !schemeDetail.getExpectedEmptyReturnWindow().before(h.getStart())) {
                                        judge.set(true);
                                        //装载时间大于还船时间
                                        if (h.getEnd()!=null && !schemeDetail.getExpectedLoadingDate().after(h.getEnd())) {
                                            judge.set(true);
                                        }else{
                                            judge.set(false);
                                        }
                                    }
                                    leaseTermStrs.add(DateUtil.format(h.getStart(), "yyyy-MM-dd HH:mm:ss") + "," + DateUtil.format(h.getEnd(), "yyyy-MM-dd HH:mm:ss"));
                                });
                            }
                        } catch (JsonProcessingException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            });
        }

        //保存船舶班期表
//        SpShipPotentialSchedule spShipSchedule = new SpShipPotentialSchedule();
//        BeanUtils.copyProperties(schemeDetail, spShipSchedule);
//        spShipSchedule.setId(IDUtil.nextId());
//        BigDecimal goodsQuantityNew = schemeDetail.getLoadingCapacity().multiply(BigDecimal.valueOf(0.985))
//                .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
//                .setScale(0, BigDecimal.ROUND_DOWN);
//        //生成年度计划，每船货上下浮动3.25%
//        BigDecimal storageCapacityFloat = goodsQuantityNew.multiply(BigDecimal.valueOf(0.0325))
//                .setScale(0, BigDecimal.ROUND_DOWN);
//        BigDecimal maxStorageCapacity = goodsQuantityNew.add(storageCapacityFloat);
//        BigDecimal minStorageCapacity = goodsQuantityNew.subtract(storageCapacityFloat);
//        spShipSchedule.setMaxStorageCapacity(maxStorageCapacity);
//        spShipSchedule.setMinStorageCapacity(minStorageCapacity);
//        spShipSchedule.setStorageCapacity(goodsQuantityNew);
//        spShipSchedule.setExecuteStatus(0);
//        if (startingDateOfLeaseBegin.get() != null) {
//            spShipSchedule.setStartingDateOfLeaseBegin(startingDateOfLeaseBegin.get());
//        }
//        if (redeliveryDateEnd.get() != null) {
//            spShipSchedule.setRedeliveryDateEnd(redeliveryDateEnd.get());
//        }
//        if (leaseTermStrs != null && leaseTermStrs.size() > 0) {
//            spShipSchedule.setLeaseTermStr(leaseTermStrs.toString());
//        }
//        shipScheduleService.saveShipSchedule(spShipSchedule);
    }

    /**
     * 校验船只时间窗口是否符合
     * @param ship
     * @param schemeDetail
     * @return
     */
    public Boolean checkShipTimeWindows(SpInformation ship,SpDispatchPotentialSchemeDetail schemeDetail){
        Boolean isSuccess = false;

        //默认空载时间等于满载时间来计算
        schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~schemeDetail.getExpectedFullLoadVoyageDays() + 1));

        //当前船只已排货时间信息
        Long count = this.baseMapper.selectCount(
                new LambdaQueryWrapper<SpDispatchPotentialSchemeDetail>()
                        .eq(SpDispatchPotentialSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                        .eq(SpDispatchPotentialSchemeDetail::getImo,ship.getImo())
                        .and(qw -> {
                            qw.between(SpDispatchPotentialSchemeDetail::getExpectedUnloadingWindow,schemeDetail.getExpectedEmptyReturnWindow(),schemeDetail.getExpectedUnloadingWindow());
                            qw.or().between(SpDispatchPotentialSchemeDetail::getExpectedEmptyReturnWindow,schemeDetail.getExpectedEmptyReturnWindow(),schemeDetail.getExpectedUnloadingWindow());
                        })
        );
        if(count > 0){
            return isSuccess;
        }

        //校验上一船货船期是否满足
        //预计空载航程天数
        SpDispatchPotentialSchemeDetail lastSchemeDetail = this.baseMapper.selectOne(
                new LambdaQueryWrapper<SpDispatchPotentialSchemeDetail>()
                        .lt(SpDispatchPotentialSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                        .eq(SpDispatchPotentialSchemeDetail::getImo,schemeDetail.getImo())
                        .orderByDesc(SpDispatchPotentialSchemeDetail::getExpectedLoadingDate)
                        .last("limit 1")
        );
        if(lastSchemeDetail != null){
            ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(), lastSchemeDetail.getExpectedUnloadThirdId()
                    , schemeDetail.getExpectedLoadThirdId(), null, null);
            if(routeDay == null){
                //与上一船货卸载港无航线，则说明到不了，不能排货
                return isSuccess;
            }
            int day = routeDay.getShipRouteDay().intValue();

            Date currentEmptyDate = DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~day - 1);

            if(currentEmptyDate.before(lastSchemeDetail.getExpectedUnloadingWindow())){
                return isSuccess;
            }
        }

        //校验当前船只下一船货船期是否满足
        SpDispatchPotentialSchemeDetail nextSchemeDetail = this.baseMapper.selectOne(
                new LambdaQueryWrapper<SpDispatchPotentialSchemeDetail>()
                        .gt(SpDispatchPotentialSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                        .eq(SpDispatchPotentialSchemeDetail::getImo,ship.getImo())
                        .last("limit 1")
        );
        if(nextSchemeDetail != null){
            ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),schemeDetail.getExpectedUnloadThirdId()
                    ,nextSchemeDetail.getExpectedLoadThirdId(),null,null);
            if(routeDay == null){
                return isSuccess;
            }
            int day = routeDay.getShipRouteDay().intValue();

            Date nextEmptyDate = DateUtil.offsetDay(nextSchemeDetail.getExpectedLoadingDate(), ~day - 1);

            if(nextEmptyDate.before(schemeDetail.getExpectedUnloadingWindow())){
                return isSuccess;
            }
        }

        if(ship.getIsVirtual().intValue() == 1){
            //虚拟船不需要校验船期
            return true;
        }

        //判断是否在出租期内
        List<ConShipping> rentShip = conShippingMapper.selectList(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,ship.getImo())
                        .ne(ConShipping::getStatus,4)
                        .eq(ConShipping::getContractType,3)
        );
        for (ConShipping conShipping : rentShip) {
            //空载时间和装载时间在租期内，则表示已租出去，不能使用
            if ((schemeDetail.getExpectedEmptyReturnWindow().before(conShipping.getRedeliveryDateEnd())
                    && schemeDetail.getExpectedEmptyReturnWindow().after(conShipping.getDeliveryDateBegin()))
                    || (schemeDetail.getExpectedUnloadingWindow().after(conShipping.getDeliveryDateBegin())
                    && schemeDetail.getExpectedUnloadingWindow().before(conShipping.getRedeliveryDateEnd()))) {
                return isSuccess;
            }
        }

        //是否处于OFF-HIRE区间
        rentShip = conShippingMapper.selectList(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,ship.getImo())
                        .eq(ConShipping::getStatus,5)
        );
        for (ConShipping conShipping : rentShip) {
            if(StringUtils.isBlank(conShipping.getHireTimeValue())){
                continue;
            }
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                List<HireTimeDetailsBO> hireTimeDetailsBOS = objectMapper.readValue(conShipping.getHireTimeValue()
                        , new TypeReference<List<HireTimeDetailsBO>>() {});
                if(hireTimeDetailsBOS.size() == 0){
                    continue;
                }
                for (HireTimeDetailsBO hireTimeDetailsBO : hireTimeDetailsBOS) {
                    Boolean inStatus = (hireTimeDetailsBO.getStart().after(schemeDetail.getExpectedEmptyReturnWindow())
                            && hireTimeDetailsBO.getStart().before(schemeDetail.getExpectedUnloadingWindow()))
                            || (hireTimeDetailsBO.getEnd().after(schemeDetail.getExpectedEmptyReturnWindow())
                            && hireTimeDetailsBO.getEnd().before(schemeDetail.getExpectedUnloadingWindow()))
                            || (hireTimeDetailsBO.getEnd().after(schemeDetail.getExpectedUnloadingWindow())
                            && hireTimeDetailsBO.getStart().before(schemeDetail.getExpectedEmptyReturnWindow()));
                    if(inStatus){
                        return isSuccess;
                    }
                }
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

        //是否在租入时间内
        rentShip = conShippingMapper.selectList(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,ship.getImo())
                        .ne(ConShipping::getStatus,4)
                        .in(ConShipping::getContractType,1,2)
        );
        for (ConShipping conShipping : rentShip) {
            if (schemeDetail.getExpectedLoadingDate().before(conShipping.getRedeliveryDateEnd())
                    && schemeDetail.getExpectedLoadingDate().after(conShipping.getDeliveryDateBegin())) {
                schemeDetail.setShipContractId(conShipping.getId());
                schemeDetail.setShipContractName(conShipping.getContractName());
                schemeDetail.setShipTradeBody(conShipping.getTradeBody());
                schemeDetail.setShipContractType(conShipping.getContractType());
                return true;
            }
        }
        return false;
    }

    /**
     * 初始化方案详情信息
     * @param year
     * @param resource
     * @param schemeId
     * @param expectLoadDate
     * @param schemeModel
     * @return
     */
    public SpDispatchPotentialSchemeDetail initDispatchSchemeDetail(int year, ConResourceMemorandum resource
            , Long schemeId, Date expectLoadDate, SpDispatchSchemeDetailModel schemeModel,ShipLoadDaysAndRouteVo shipLoadDaysAndRouteVo){
        SpDispatchPotentialSchemeDetail schemeDetail = new SpDispatchPotentialSchemeDetail();
        //装载港
        schemeDetail.setExpectedLoadingPort(shipLoadDaysAndRouteVo.getStartPortName());
        schemeDetail.setExpectedLoadingPortId(shipLoadDaysAndRouteVo.getStartPortId());
        //卸载港
        schemeDetail.setExpectedUnloadingPort(shipLoadDaysAndRouteVo.getEndPortName());
        schemeDetail.setExpectedUnloadingPortId(shipLoadDaysAndRouteVo.getEndPortId());
        schemeDetail.setPassStrait(shipLoadDaysAndRouteVo.getPassStrait());
        schemeDetail.setPassArea(shipLoadDaysAndRouteVo.getPassArea());
        schemeDetail.setExpectedLoadThirdId(shipLoadDaysAndRouteVo.getStartThirdPortId());
        schemeDetail.setExpectedUnloadThirdId(shipLoadDaysAndRouteVo.getEndThirdPortId());
        schemeDetail.setShipSpeed(schemeModel.getSpeed());
        schemeDetail.setUnloadDistance(shipLoadDaysAndRouteVo.getTrackDistance());

        int shipDays = 0;
        int chinaDays = 0;
        int europeDays = 0;
        int indiaDays = 0;

        if (shipLoadDaysAndRouteVo.getShipDay() != null) {
            shipDays = shipLoadDaysAndRouteVo.getShipDay();
        }
        if (shipLoadDaysAndRouteVo.getChinaDays() != null) {
            chinaDays = shipLoadDaysAndRouteVo.getChinaDays();
        }
        if (shipLoadDaysAndRouteVo.getEuropeDays() != null) {
            europeDays = shipLoadDaysAndRouteVo.getEuropeDays();
        }
        if (shipLoadDaysAndRouteVo.getIndiaDays() != null) {
            indiaDays = shipLoadDaysAndRouteVo.getIndiaDays();
        }
        schemeDetail.setPlanYear(year);
        schemeDetail.setCreateBy(SecurityUtils.getUsername());
        schemeDetail.setId(IDUtil.nextId());
        schemeDetail.setSchemeId(schemeId);
        schemeDetail.setSchemeNumber(1);
        schemeDetail.setSchemeApprove(1);

        // 从资源合同服务获取资源方信息
        if (resource != null) {
            schemeDetail.setLoadingCapacity(resource.getLoadingOrUnloadingCapacity());
            schemeDetail.setLoadingCapacityUnit(resource.getUnit());
            schemeDetail.setResourceContract(schemeModel.getConId());
            if (resource.getContractName() != null) {
                schemeDetail.setContractName(resource.getContractName());
            }
            schemeDetail.setSeller(resource.getSeller());
            if (resource.getTradeType() != null) {
                schemeDetail.setTradeType(resource.getTradeType());
            }
            if (resource.getTradeBody() != null) {
                schemeDetail.setTradeBody(resource.getTradeBody());
            }
            schemeDetail.setLoadingCapacity(resource.getLoadingOrUnloadingCapacity());
            if (resource.getBuyer() != null) {
                schemeDetail.setBuyer(resource.getBuyer());
            }
        }else{
            schemeDetail.setSeller("潜在");
        }

        schemeDetail.setVoyageNo("CNOOC" + year + "" + 01);
        schemeDetail.setGoodsNum(schemeDetail.getSeller() + "-FOB-" + 1);
        //换港时间 1:合同中的换港时间约定，BP，窗口前 75 天，马石油中期，窗口所在月份第一天的前 60 天
        //换港时间 2:合同中的换港时间约定，BP，窗口前 60 天，马石油中期，窗口前 60 天
        if (resource != null && StringUtils.isNotBlank(resource.getChangePortTimeLoading())) {
            Date changePortTimeLoading = DateUtil.parse(resource.getChangePortTimeLoading(), "yyyy-MM-dd HH:mm:ss");
            if (schemeDetail.getSeller().equals("BP")) {
                schemeDetail.setPortChangeDateOne(DateUtil.offsetDay(changePortTimeLoading, -75));
                schemeDetail.setPortChangeDateTwo(DateUtil.offsetDay(changePortTimeLoading, -60));
            } else if (schemeDetail.getSeller().equals("马石油") && resource.getTradeType().equals(2)) {
                schemeDetail.setPortChangeDateOne(DateUtil.offsetDay(changePortTimeLoading, -60));
                schemeDetail.setPortChangeDateTwo(DateUtil.offsetDay(changePortTimeLoading, -60));
            }
        }

        //换船/指船时间 1:合同中的约定，BP，窗口前 45 天，马石油中期，窗口所在月份第一天的前 20 天
        //换船时间 2:合同中的约定，BP，窗口前 25 天，马石油中期，窗口前 20 天
        if (resource != null && StringUtils.isNotBlank(resource.getChangeShipTime())) {
            Date changeShipTime = DateUtil.parse(resource.getChangeShipTime(), "yyyy-MM-dd HH:mm:ss");
            if (schemeDetail.getSeller().equals("BP")) {
                schemeDetail.setShipChangeDateOne(DateUtil.offsetDay(changeShipTime, -45));
                schemeDetail.setShipChangeDateTwo(DateUtil.offsetDay(changeShipTime, -25));
            } else if (schemeDetail.getSeller().equals("马石油") && resource.getTradeType().equals(2)) {
                schemeDetail.setShipChangeDateOne(DateUtil.offsetDay(changeShipTime, -20));
                schemeDetail.setShipChangeDateTwo(DateUtil.offsetDay(changeShipTime, -20));
            }
        }

        schemeDetail.setExpectedLoadingDate(expectLoadDate);
        schemeDetail.setExpectedLoadingCompletionWindow(DateUtil.offsetDay(expectLoadDate, 2));

        //预计卸载时间
        schemeDetail.setExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, shipDays));
        //中国预计卸载时间
        schemeDetail.setChinaExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, chinaDays));
        //印度预计卸载时间
        schemeDetail.setIndiaUnloadTime(DateUtil.offsetDay(expectLoadDate, indiaDays));
        //欧洲预计卸载时间
        schemeDetail.setEuropeUnloadTime(DateUtil.offsetDay(expectLoadDate, europeDays));
        //预计满载航程天数
        schemeDetail.setExpectedFullLoadVoyageDays(shipDays);
        //印度空载航程天数
        schemeDetail.setIndiaEmptyLoadDays(indiaDays);
        //欧洲空载航程天数
        schemeDetail.setEuropeEmptyLoadDays(europeDays);
        //预计卸载完成窗口
        schemeDetail.setExpectedUnloadingCompletionWindow(DateUtil.offsetDay(schemeDetail.getExpectedUnloadingWindow(), 2));

        //预计空载返程时间
        schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~shipDays + 1));
        //中国预计空载返程窗口
        schemeDetail.setChinaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~chinaDays + 1));
        //欧洲预计空载返程窗口
        schemeDetail.setEuropeExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~europeDays + 1));
        //印度预计空载返程窗口
        schemeDetail.setIndiaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~indiaDays + 1));

        schemeDetail.setCreateDate(new Date());
        schemeDetail.setShipSpeed(schemeModel.getSpeed());
        schemeDetail.setQuantityUnit(schemeModel.getQuantityUnit());
        //TODO:巴拿马运河预定时间:装载时间提前30天 写死，后面从字典表中获取
        schemeDetail.setPanamaCanalReservationDate(DateUtil.offsetDay(expectLoadDate, -30));
        return schemeDetail;
    }

}
